package storage

import (
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/holiman/uint256"
)

//单笔交易执行的缓存并不需要考虑并发，所以这里的cache只用普通的map来做
type ExecuteCache struct {
	//判断该交易执行的时候是否会创建新的Contract，和Code和Nonce Cache有关
	IsCreate bool
	//判断交易是否会Destruct一些合约地址，和Balance Cache有关
	IsDestruct bool
	//判断交易执行的时候是否打印了日志
	IsLog bool

	// Balance & Destructs Cache
	// 中途发生的Call也会改变Address的Balance
	BalanceCache balanceCache

	//SLOAD & SSTORE operation Cache
	DataCache dataCache

	//LOGs Cache
	LogCache logCache

	//DestructCache
	DestructCache destructCache

	//CREATE & CREATE2 & CodeRelated Cache
	NonceCache       nonceCache
	NewContractCache newContractCache
}

type cache map[common.Hash]common.Hash
type dataCache map[common.Address]*cache

type balanceCache map[common.Address]*uint256.Int
type destructCache map[common.Address]bool

type nonceCache map[common.Address]uint64
type newContractCache map[common.Address]*newContract

type newContract struct {
	Nonce    uint64
	Code     []byte
	CodeSize int
	CodeHash common.Hash
}

type logCache map[common.Address][]*types.Log

func (Ec *ExecuteCache) GetState(addr common.Address, hash common.Hash) (common.Hash, bool) {
	state, ok := Ec.DataCache[addr]
	if !ok {
		return common.Hash{}, false
	} else {
		data, ok := (*state)[hash]
		if ok {
			return data, true
		} else {
			return common.Hash{}, false
		}
	}
}

func (Ec *ExecuteCache) SetState(addr common.Address, hash common.Hash, value common.Hash) {
	state, ok := Ec.DataCache[addr]
	if ok {
		(*state)[hash] = value
	} else {
		newCache := make(cache)
		Ec.DataCache[addr] = &newCache
		(*Ec.DataCache[addr])[hash] = value
	}
}

func (Ec *ExecuteCache) AddLog(addr common.Address, log *types.Log) {
	if !Ec.IsLog {
		Ec.IsLog = true
	}
	if Ec.LogCache[addr] == nil {
		var list []*types.Log
		Ec.LogCache[addr] = append(list, log)
	} else {
		list := Ec.LogCache[addr]
		Ec.LogCache[addr] = append(list, log)
	}

}

//Copy会生成一个新的ExecuteCache结构体，考虑到性能直接手动拷贝
func (Ec *ExecuteCache) Copy() *ExecuteCache {
	temp := &ExecuteCache{
		IsCreate:   Ec.IsCreate,
		IsDestruct: Ec.IsDestruct,
		IsLog:      Ec.IsLog,
	}
	//深拷贝BalanceModifyCache
	newBalanceModify := balanceCache{}
	for key, value := range Ec.BalanceCache {
		newBalanceModify[key] = value.Clone()
	}
	temp.BalanceCache = newBalanceModify
	//深拷贝DataCache
	newDataCache := Ec.DataCache.Copy()
	temp.DataCache = *newDataCache

	//根据Flag深拷贝Cache
	if Ec.IsLog {
		temp.LogCache = *Ec.LogCache.Copy()
	} else {
		temp.LogCache = logCache{}
	}

	if Ec.IsDestruct {
		temp.DestructCache = *Ec.DestructCache.Copy()
	} else {
		temp.DestructCache = destructCache{}
	}

	if Ec.IsCreate {
		temp.NonceCache = *Ec.NonceCache.Copy()
		temp.NewContractCache = *Ec.NewContractCache.Copy()
	} else {
		temp.NonceCache = nonceCache{}
		temp.NewContractCache = newContractCache{}
	}

	return temp
}

func (Dc *dataCache) Copy() *dataCache {
	newDataCache := &dataCache{}
	for key, value := range *Dc {
		newCache := &cache{}
		for k, v := range *value {
			(*newCache)[k] = v
		}
		(*newDataCache)[key] = newCache
	}
	return newDataCache
}

func (Lc *logCache) Copy() *logCache {
	newLogCache := &logCache{}
	for key, value := range *Lc {
		var list []*types.Log
		for _, log := range value {
			newLog := &types.Log{
				Address:     log.Address,
				Topics:      log.Topics,
				Data:        log.Data,
				BlockNumber: log.BlockNumber,
				TxHash:      log.TxHash,
				BlockHash:   log.BlockHash,
				Index:       log.Index,
				Removed:     log.Removed,
			}
			list = append(list, newLog)
		}
		(*newLogCache)[key] = list
	}
	return newLogCache
}

func (Dc *destructCache) Copy() *destructCache {
	newCache := &destructCache{}
	for key, value := range *Dc {
		(*newCache)[key] = value
	}
	return newCache
}

func (Nc *nonceCache) Copy() *nonceCache {
	newCache := &nonceCache{}
	for key, value := range *Nc {
		(*newCache)[key] = value
	}
	return newCache
}

func (Nc *newContractCache) Copy() *newContractCache {
	newCache := &newContractCache{}
	for key, value := range *Nc {
		temp := newContract{
			Nonce:    value.Nonce,
			Code:     value.Code,
			CodeSize: value.CodeSize,
			CodeHash: value.CodeHash,
		}
		(*newCache)[key] = &temp
	}
	return newCache
}
